﻿using System;
using System.Collections.Generic;
using System.Data;

using BizElements.TemplateEngine;
using BizElements.Core;

namespace BizElements.Generator.OrmComponents
{
    /// <summary>Enumerates SP input component types.</summary>
    public static class SpComponentType
    {
        #region Predefined types.

        /// <summary>Instance property contains <see cref="BizElements.Generator.StoredProcedureWrapperClass"/> object.</summary>
        public static readonly ComponentType SpArray = new ComponentType(Guid.NewGuid());
        /// <summary>Instance property contains <see cref="BizElements.Generator.StoredProcedure"/> object.</summary>
        public static readonly ComponentType SP = new ComponentType(Guid.NewGuid());
        /// <summary>Instance property contains <see cref="BizElements.Generator.IStoredProcedureParameter"/> object.</summary>
        public static readonly ComponentType SpParameter = new ComponentType(Guid.NewGuid());

        #endregion
    }

    /// <summary>Builds <see cref="SpComponentType.SpArray"/> input components.</summary>
    public sealed class SpComponentBuilder : ITemplateInputComponentBuilder
    {
        #region ITemplateInputComponentBuilder.

        /// <summary>Builds <see cref="SpComponentType.SpArray"/> input components.</summary>
        public ITemplateInputComponent Build(object instance)
        {
            return new SpComponent((StoredProcedureWrapperClass)instance);
        }

        /// <summary>Checks that the provided object is <see cref="BizElements.Generator.StoredProcedureWrapperClass"/>.</summary>
        public bool IsCompatibleItem(object instance)
        {
            return instance is StoredProcedureWrapperClass;
        }

        #endregion
    }

    /// <summary>SP input component. One of the types enumerated in <see cref="SpComponentType"/> class.</summary>
    public sealed class SpComponent : TemplateInputComponentBase
    {
        #region CTor.

        /// <summary>Creates a root component. Instance property contains <see cref="BizElements.Generator.StoredProcedureWrapperClass"/> object.</summary>
        public SpComponent(StoredProcedureWrapperClass procedures)
            : base(SpComponentType.SpArray, procedures, "SP", null, null)
        {
        }

        private SpComponent(ComponentType type, object obj, string name, ITemplateInputComponent parent)
            : base(type, obj, name, null, parent)
        {
        }

        #endregion

        #region Ensure/build sub-components list.

        /// <summary>Ensures that Subcomponents property is initialized.</summary>
        protected override void EnsureSubcomponents()
        {
            if (this.Subcomponents != null)
                return;

            this.Subcomponents = new List<ITemplateInputComponent>();
            AppendSubComponentsForArray();
            AppendSubComponentForProcedure();
        }

        private void AppendSubComponentsForArray()
        {
            StoredProcedureWrapperClass wrapper = this.Instance as StoredProcedureWrapperClass;
            if (wrapper != null)
            {
                foreach (StoredProcedure sp in wrapper.Procedures)
                    this.Subcomponents.Add(new SpComponent(SpComponentType.SP, sp, sp.ProcedureName, this));
            }
        }

        private void AppendSubComponentForProcedure()
        {
            StoredProcedure sp = this.Instance as StoredProcedure;
            if (sp != null)
            {
                foreach (var par in sp.Parameters)
                    this.Subcomponents.Add(new SpComponent(SpComponentType.SpParameter, par, par.ParameterName, this));
            }
        }

        #endregion

        /// <summary>Gets stored procedure name.</summary>
        /// <returns>Array of string taht contains exactly one item: stored procedure name.</returns>
        protected override string[] GetComponentKeys()
        {
            return new string[] { this.Name };
        }
    }

    /// <summary>Contains extension methods for SP input components which may be used in templates.</summary>
    public static class SpComponentExtensions
    {
        /// <summary>Gets SP wrapper method parameter/argument name. Camel case.</summary>
        /// <param name="component">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Generator.IStoredProcedureParameter"/> object.</param>
        public static string MethodParameterName(this ITemplateInputComponent component)
        {
            return DbComponentExtensionMethods.ClassItemTemplate.GenerateLocalVariable(component.Name);
        }

        /// <summary>Gets SP wrapper method name. Pascal case.</summary>
        /// <param name="storedProcedure">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Generator.StoredProcedure"/> object.</param>
        public static string MethodName(this ITemplateInputComponent storedProcedure)
        {
            StoredProcedure sp = (StoredProcedure)storedProcedure.Instance;
            string spNameWoPrefix = TextUtil.StripPrefix(sp.ProcedureName, sp.PrefixesToBeStripped);
            return DbComponentExtensionMethods.ClassItemTemplate.GenerateMethodName(spNameWoPrefix);
        }

        /// <summary>Gets SP wrapper method parameter/argument type.</summary>
        /// <param name="spParameter">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Generator.IStoredProcedureParameter"/> object.</param>
        public static string MethodArgumentType(this ITemplateInputComponent spParameter)
        {
            StoredProcedureWrapperClass wrapper = (StoredProcedureWrapperClass)spParameter.Root.Instance;
            IStoredProcedureParameter par = (IStoredProcedureParameter)spParameter.Instance;
            if (par.IsValueType)
                return par.NullableType(wrapper.TypeMapper);
            else
                return par.Type;
        }

        /// <summary>Gets SP wrapper method parameter/argument modifier keyword (none, out or ref).</summary>
        /// <param name="spParameter">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Generator.IStoredProcedureParameter"/> object.</param>
        public static string MethodParameterKeyword(this ITemplateInputComponent spParameter)
        {
            IStoredProcedureParameter par = (IStoredProcedureParameter)spParameter.Instance;
            return par.MethodParameterKeyword;
        }

        /// <summary>Generates code (hard coded) that creates a new instance of <see cref="BizElements.Core.DbParameter"/> class for give parameter meta data.</summary>
        /// <param name="spParameter">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Generator.IStoredProcedureParameter"/> object.</param>
        public static string NewDbParameter(this ITemplateInputComponent spParameter)
        {
            IStoredProcedureParameter par = (IStoredProcedureParameter)spParameter.Instance;
            StoredProcedureWrapperClass wrapper = (StoredProcedureWrapperClass)spParameter.Root.Instance;
            string ctor;
            if (par.PassMode == SpArgumentPassMode.DataTableFilledByAdapter)
            {
                // E.g. Oracle Cursor - these are handled differently by DB interaction layer of OR/M framework.
                ctor = "new DbParameter(\"" +
                    par.ParameterName + "\", DbType." + par.DbType + ", " + par.Size + ", " +
                    "ParameterDirection." + par.NetDirection + ", true, 0, 0, null, DataRowVersion.Proposed, " +
                    "null, SpArgumentPassMode.DataTableFilledByAdapter" + ")";
            }
            else if (par.IsValueType)
            {
                ctor = "new DbParameter(\"" +
                    par.ParameterName + "\", DbType." + par.DbType + ", " + par.Size + ", " +
                    "ParameterDirection." + par.NetDirection + ", true, 0, 0, null, DataRowVersion.Proposed, " +
                    "((" + par.IsNullExpression(wrapper.TypeMapper, par.LocalVariableName) + ") ? (object) DBNull.Value : (object) " + par.LocalVariableName + ".Value))";
            }
            else
            {
                ctor = "new DbParameter(\"" +
                    par.ParameterName + "\", DbType." + par.DbType + ", " + par.Size + ", " +
                    "ParameterDirection." + par.NetDirection + ", true, 0, 0, null, DataRowVersion.Proposed, " +
                    "((" + par.IsNullExpression(wrapper.TypeMapper, par.LocalVariableName) + ") ? (object) DBNull.Value : (object) " + par.LocalVariableName + "))";
            }

            return ctor;
        }
    }

    /// <summary>Contains methods/predicates which evaluate SP parameter criteria.</summary>
    public static class SpParameterCriteria
    {
        /// <summary>Returns <b>true</b> if given parameter has <b>input</b> direction.</summary>
        /// <param name="spParameter">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Generator.IStoredProcedureParameter"/> object.</param>
        public static bool IsInput(ITemplateInputComponent spParameter)
        {
            IStoredProcedureParameter par = (IStoredProcedureParameter)spParameter.Instance;
            return (par.Direction == ParameterDirection.Input);
        }

        /// <summary>Returns <b>true</b> if given parameter has <b>output</b> direction.</summary>
        /// <param name="spParameter">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Generator.IStoredProcedureParameter"/> object.</param>
        public static bool IsOutput(ITemplateInputComponent spParameter)
        {
            IStoredProcedureParameter par = (IStoredProcedureParameter)spParameter.Instance;
            return (par.Direction == ParameterDirection.Output);
        }

        /// <summary>Returns <b>true</b> if given parameter has both <b>input</b> and <b>output</b> direction.</summary>
        /// <param name="spParameter">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Generator.IStoredProcedureParameter"/> object.</param>
        public static bool IsInputOutput(ITemplateInputComponent spParameter)
        {
            IStoredProcedureParameter par = (IStoredProcedureParameter)spParameter.Instance;
            return (par.Direction == ParameterDirection.InputOutput);
        }

        /// <summary>Returns <b>true</b> if given parameter is a return value.</summary>
        /// <param name="spParameter">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Generator.IStoredProcedureParameter"/> object.</param>
        public static bool IsReturnValue(ITemplateInputComponent spParameter)
        {
            IStoredProcedureParameter par = (IStoredProcedureParameter)spParameter.Instance;
            return (par.Direction == ParameterDirection.ReturnValue);
        }

        /// <summary>Returns <b>true</b> if given parameter is primitive, ie. not a DataTable filled by adapter as required in Oracle and similar DBMSs.</summary>
        /// <param name="spParameter">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Generator.IStoredProcedureParameter"/> object.</param>
        public static bool IsNotDataTableFilledByAdapter(ITemplateInputComponent spParameter)
        {
            IStoredProcedureParameter par = (IStoredProcedureParameter)spParameter.Instance;
            return (par.PassMode != SpArgumentPassMode.DataTableFilledByAdapter);
        }

        /// <summary>Returns <b>true</b> if given parameter has a <b>ref</b> or <b>out</b> .NET argument modifier.</summary>
        /// <param name="spParameter">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Generator.IStoredProcedureParameter"/> object.</param>
        public static bool IsRefOrOut(ITemplateInputComponent spParameter)
        {
            IStoredProcedureParameter par = (IStoredProcedureParameter)spParameter.Instance;
            return !string.IsNullOrEmpty(par.MethodParameterKeyword);
        }
    }

    /// <summary>Contains methods/predicates which evaluate SP criteria.</summary>
    public static class SpCriteria
    {
        /// <summary>Returns <b>true</b> if return value parameter should be generated.</summary>
        public static bool ShouldGenerateReturnValue(ITemplateInputComponent sp)
        {
            StoredProcedure proc = (StoredProcedure)sp.Instance;
            return proc.ShouldGenerateReturnValue;
        }
    }
}